import * as cheerio from 'cheerio'
import imageSize from 'image-size'
import mime from 'mime'

import fs from 'node:fs'

let host = 'https://www.photoroom.com/'

const getHTMLText = async () => {
  try {
    let res = await fetch(host)

    let text = await res.text()

    return text
  } catch (error) {
    console.error('获取网页内容失败', error)
  }
}

/**
 * 获取外部链接的css的 图片地址
 */
const getCssLinksImages = ($) => {
  let images = []

  // 拿到所有的 style 标签
  $('style').each((index, element) => {
    const styleContent = $(element).html()
    const matches = styleContent.match(/background(?:-image)?:\s*url\(['"]?(.*?)['"]?\)/g)
    if (matches) {
      matches.forEach((match) => {
        const urlMatch = match.match(/url\(['"]?(.*?)['"]?\)/)
        if (urlMatch && urlMatch[1] && !urlMatch[1].startsWith('data:image')) {
          images.push(urlMatch[1])
        }
      })
    }
  })

  return images
}

/**
 * 获取 <img src=''> 这种的图片地址
 * @returns
 */
const getImageTagImages = ($) => {
  let images = []

  // 提取 img 标签中的图片链接
  $('img').each((index, element) => {
    // 当然，这里可能会存在有 懒加载的情况， 具体网站，具体分析，只要是懒加载，一般是解析 data-src= 地址
    const src = $(element).attr('src')
    if (src && src.indexOf('data:image') === -1) {
      images.push(src)
    }
  })

  return images
}

/**
 * 获取行内样式中的背景图片链接
 */
const getInlineStyleTagImages = ($) => {
  let images = []

  // 提取行内样式中的背景图片链接
  $('[style]').each((index, element) => {
    const style = $(element).attr('style')
    const match = style.match(/background(?:-image)?:\s*url\(['"]?(.*?)['"]?\)/g)
    if (match && match[1]) {
      const imageUrl = match[1]
      if (!imageUrl.startsWith('data:image')) {
        images.push(imageUrl)
      }
    }
  })

  return images
}

/**
 * 获取 style 标签内的 图片资源
 */
const getStyleTagsImages = ($) => {
  let images = []

  // 提取style 标签内的 图片资源
  $('style').each((index, element) => {
    const styleContent = $(element).html()
    const matches = styleContent.match(/background(?:-image)?:\s*url\(['"]?(.*?)['"]?\)/g)
    if (matches) {
      matches.forEach((match) => {
        const urlMatch = match.match(/url\(['"]?(.*?)['"]?\)/)
        if (urlMatch && urlMatch[1] && !urlMatch[1].startsWith('data:image')) {
          images.push(urlMatch[1])
        }
      })
    }
  })

  return images
}

/**
 * 解析网站所有的图片资源
 */
const resolveImage = (text) => {
  let $ = cheerio.load(text)

  let imageTags = getImageTagImages($)

  let inlineImages = getInlineStyleTagImages($)

  let styleTagImages = getStyleTagsImages($)

  // 提取外部css链接中的图片资源
  let cssLinksImages = getCssLinksImages($, text)

  return imageTags.concat(inlineImages, styleTagImages, cssLinksImages)
}

const resolveImageSize = async (list) => {
  let count = 0

  let allImagePromise = list.map((p) => {
    return fetch(p, {
      headers: { responseType: 'arraybuffer' }
    }).then(async (response) => {
      count++

      console.log('图片信息解析成功：', count)
      const buffer = await response.arrayBuffer()
      const uint8Array = new Uint8Array(buffer)

      let item = await imageSize(uint8Array)

      return {
        src: p,
        width: item.width,
        height: item.height,
        // 图片类型
        type: mime.getType(item.type)
      }
    })
  })

  try {
    let result = await Promise.all(allImagePromise)

    // 过滤掉 svg这样小的图片， 一般不会作为结构化数据
    return result.filter(p => p.type !== 'image/svg+xml')
  } catch (error) {
    console.log('图片宽高解析失败')
    console.log(error)
  }
}

/**
 * 包装成 application/ld+json 结构化数据
 * @param {*} list
 * @returns
 */
const wrapJsonLd = (list) => {
  return list.map((item) => {
    return {
      '@context': 'https://schema.org',
      '@type': 'ImageObject',
      url: item.src,
      width: item.width,
      height: item.height,
      encodingFormat: `${item.type}`
    }
  })
}

const start = async () => {
  let text = await getHTMLText()

  if (!text) {
    console.log('网站的内容没有解析到')
    return
  }

  let imageList = resolveImage(text)

  // 需要去补全 域名
  imageList = imageList.map((item) => {
    return item.startsWith('http') ? item : host + item
  })

  // 解析图片的宽高
  let imageSizeList = await resolveImageSize(imageList)

  // 包装 application/ld+json 结构化数据
  let jsonLd = wrapJsonLd(imageSizeList)

  fs.writeFile('./web-application.json', JSON.stringify(jsonLd, null, 2), (err) => {
    if (err) {
      console.log('结构化数据写入失败')
      console.log(err)
      return
    }

    console.log('结构化数据写入成功')
  })
}

start()
